Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit 68c26a697b4f0172d935dffd94a04348b7880e4b


Parents : da201fb
Author : Mark Qvist <bc7291552be7a58f361522990465165c>
Date : 2026-05-19T14:50:31+02:00

Added sys/notice timeout config

Changes

4 files changed, 100 insertions(+), 14 deletions(-)


Diff

diff --git a/nomadnet/NomadNetworkApp.py b/nomadnet/NomadNetworkApp.py
index 9a9a500..4e14b50 100644
--- a/nomadnet/NomadNetworkApp.py
+++ b/nomadnet/NomadNetworkApp.py
@@ -154,6 +154,7 @@ class NomadNetworkApp:
self.rrc_history_per_room_cap = 500
self.rrc_filter_loaded_history = True
+ self.rrc_ephemeral_notices = 600
if not os.path.isdir(self.storagepath):
os.makedirs(self.storagepath)
@@ -918,6 +919,11 @@ class NomadNetworkApp:
try: value = self.config["rrc"].as_bool(option)
except Exception: value = True
self.rrc_filter_loaded_history = value
+
+ if option == "ephemeral_notices":
+ try: value = self.config["rrc"].as_float(option)
+ except Exception: value = 0
+ self.rrc_ephemeral_notices = value*60
if "node" in self.config:
if not "enable_node" in self.config["node"]:
@@ -1248,6 +1254,13 @@ history_per_room_cap = 500
# loaded.
filter_loaded_history = yes
+# You can choose whether notices and sys
+# messages persist indefinitely, or are
+# removed from the message history after
+# the specified time in minutes. Set to 0
+# to disable and keep forever.
+ephemeral_notices = 10
+
[node]
# Whether to enable node hosting

diff --git a/nomadnet/RRC.py b/nomadnet/RRC.py
index 6cc28ab..1335fe1 100644
--- a/nomadnet/RRC.py
+++ b/nomadnet/RRC.py
@@ -201,6 +201,9 @@ class RRCHub:
STATUS_CONNECTED = 2
STATUS_FAILED = 3
+ CLEAN_HISTORY_INTERVAL = 5
+ SYS_NOTICE_TIMEOUT = 600
+
def __init__(self, manager, hub_hash, dest_name=None, name=None):
self.manager = manager
self.hub_hash = hub_hash
@@ -242,6 +245,8 @@ class RRCHub:
self._reconnect_attempts = 0
self._reconnect_timer = None
self._pending_pings = {}
+ self._last_history_clean = 0
+ self.clean_last_removed = 0
self.available_rooms = {}
self._silent_list_pending = 0
@@ -631,6 +636,11 @@ class RRCHub:
except Exception: return True
return v
+ def _ephemeral_notices_history(self):
+ try: v = getattr(self.manager.app, "rrc_ephemeral_notices", self.SYS_NOTICE_TIMEOUT)
+ except Exception: return self.SYS_NOTICE_TIMEOUT
+ return v
+
def _entry_for(self, msg):
return {
H_KIND: msg.kind,
@@ -723,6 +733,32 @@ class RRCHub:
with self._lock:
self.messages[room] = msgs
+ def _clean_history(self):
+ now = time.time()
+ cleaned = False
+ remove_after = self._ephemeral_notices_history()
+ if now > self._last_history_clean + self.CLEAN_HISTORY_INTERVAL:
+ RNS.log(f"Cleaning loaded message history", RNS.LOG_DEBUG)
+ with self._lock:
+ try:
+ for r in self.messages:
+ old = set()
+ for m in self.messages[r]:
+ age = now-m.ts/1000.0
+ should_filter = False
+ if m.kind == "system": should_filter = True
+ elif m.kind == "notice": should_filter = True
+ if should_filter and age > remove_after: old.add(m)
+
+ for m in old:
+ self.messages[r].remove(m)
+ cleaned = True
+
+ except Exception as e: RNS.trace_exception(e)
+
+ self._last_history_clean = time.time()
+ if cleaned: self.clean_last_removed = time.time()
+
def _record_message(self, msg, local=False):
cap = self._per_room_cap()
with self._lock:
@@ -736,6 +772,7 @@ class RRCHub:
if msg.mention:
self.mention_rooms.add(msg.room)
self._append_history(msg.room, msg)
+ self._clean_history()
self.manager._notify_messages(self, msg)
def _record_system(self, room, text):
@@ -749,6 +786,7 @@ class RRCHub:
if cap is not None and len(buf) > cap:
del buf[:len(buf)-cap]
self._append_history(room, msg)
+ self._clean_history()
self.manager._notify_messages(self, msg)
def _record_notice(self, msg):
@@ -772,6 +810,7 @@ class RRCHub:
self.unread_rooms.add(target_room)
if target_room:
self._append_history(target_room, msg)
+ self._clean_history()
self.manager._notify_messages(self, msg)
def get_messages(self, room):

diff --git a/nomadnet/ui/textui/Channels.py b/nomadnet/ui/textui/Channels.py
index b763182..513c88f 100644
--- a/nomadnet/ui/textui/Channels.py
+++ b/nomadnet/ui/textui/Channels.py
@@ -29,13 +29,6 @@ _LINK_RE = re.compile(
r"|(?P<room>(?<!\w)#[A-Za-z0-9][A-Za-z0-9_\-]{0,62})"
)
-
-
-
-
-
-
-
def _link_attrs():
return {
"room": urwid.AttrSpec("light cyan,underline", "default", colors=256),
@@ -43,10 +36,8 @@ def _link_attrs():
"page": urwid.AttrSpec("light blue,underline", "default", colors=256),
}
-
_LINK_ATTRS = _link_attrs()
-
def _scan_links(text):
for m in _LINK_RE.finditer(text):
if m.group("lxmf"):
@@ -366,6 +357,7 @@ class RoomWidget(urwid.WidgetWrap):
self.app = nomadnet.NomadNetworkApp.get_shared_instance()
self.messagelist = None
+ self.last_history_clean = 0
self.peer_info_widget = urwid.AttrMap(urwid.Text(""), "msg_header_sent")
self._update_peer_info()
@@ -489,11 +481,33 @@ class RoomWidget(urwid.WidgetWrap):
try:
widget = _message_widget(self.app, self.hub, msg, link_delegate=self.link_delegate)
wrapped = urwid.AttrMap(widget, None)
- body = self.messagelist._listbox.body
+ body = self.messagelist.get_body()
was_at_bottom = getattr(self, "_empty_placeholder", False) or getattr(self.messagelist, "bottom_is_visible", True)
if getattr(self, "_empty_placeholder", False):
del body[:]
self._empty_placeholder = False
+
+ if self.hub.clean_last_removed > self.last_history_clean:
+ try:
+ hub_msgs = self.hub.get_messages(self.room) if (self.hub is not None and self.room is not None) else []
+ self.last_history_clean = time.time()
+ c = self.messagelist.body_len()
+ old = set()
+ for i in range(0, c):
+ msg = None
+ w = self.messagelist.get_item(i)
+ if hasattr(w, "_original_widget"): o = w._original_widget
+ else: o = None
+ if hasattr(o, "msg"): msg = o.msg
+ elif hasattr(w, "msg"): msg = w.msg
+ if msg and not msg in hub_msgs: old.add(i)
+
+ for i in reversed(list(old)): self.messagelist.delete_position(i)
+
+ except Exception as e:
+ RNS.log("Error while cleaning room history", RNS.LOG_ERROR)
+ RNS.trace_exception(e)
+
body.append(wrapped)
cap = getattr(self.app, "rrc_history_per_room_cap", 0)
if cap and cap > 0:
@@ -865,17 +879,23 @@ def _message_widget(app, hub, m, link_delegate=None):
evt_icon = g["arrow_l"] if m.text.endswith(" left") else g["arrow_r"]
spans, has_links = _body_markup(m.text or "", body_attr="irc_system", own_nick=own_nick)
markup = [_ts_prefix(m.ts), ("irc_system", evt_icon+" ")] + spans
- return _wrap_text(markup, link_delegate if has_links else None)
+ final_widget = _wrap_text(markup, link_delegate if has_links else None)
+ final_widget.msg = m
+ return final_widget
if m.kind == "notice":
spans, has_links = _body_markup(m.text or "", body_attr="irc_notice", own_nick=own_nick)
markup = [_ts_prefix(m.ts), ("irc_notice", g["info"]+" ")] + spans
- return _wrap_text(markup, link_delegate if has_links else None)
+ final_widget = _wrap_text(markup, link_delegate if has_links else None)
+ final_widget.msg = m
+ return final_widget
if m.kind == "error":
spans, has_links = _body_markup(m.text or "", body_attr="irc_error", own_nick=own_nick)
markup = [_ts_prefix(m.ts), ("irc_error", g["warning"]+" ")] + spans
- return _wrap_text(markup, link_delegate if has_links else None)
+ final_widget = _wrap_text(markup, link_delegate if has_links else None)
+ final_widget.msg = m
+ return final_widget
own = False
try:
@@ -895,7 +915,9 @@ def _message_widget(app, hub, m, link_delegate=None):
body = m.text or ""
spans, has_links = _body_markup(body, body_attr="body_text", own_nick=None if own else own_nick)
markup = [_ts_prefix(m.ts), (nick_attr, "<"+sender+">"), ("body_text", " ")] + spans
- return _wrap_text(markup, link_delegate if has_links else None)
+ final_widget = _wrap_text(markup, link_delegate if has_links else None)
+ final_widget.msg = m
+ return final_widget
def _wrap_text(markup, link_delegate):

diff --git a/nomadnet/ui/textui/Guide.py b/nomadnet/ui/textui/Guide.py
index 6c5a292..66def19 100644
--- a/nomadnet/ui/textui/Guide.py
+++ b/nomadnet/ui/textui/Guide.py
@@ -781,6 +781,18 @@ This section hold configuration directives related to the RRC client behaviour.
Maximum number of messages retained per room in the in-memory scrollback buffer, and the number of messages restored from on-disk history at startup. The on-disk log itself is appended to indefinitely; this cap only controls how much backlog is visible. Set to 0 to keep every message in memory.
<
+>>>
+`!filter_loaded_history = yes`!
+>>>>
+Whether to filter notices and system message when initially loading room history.
+<
+
+>>>
+`!ephemeral_notices = 10`!
+>>>>
+If enabled, notices and system messages will disappear from the history after a while. 0 disables, otherwise sets the timeout in minutes.
+<
+
>> Text UI Section
This section hold configuration directives related to the look and feel of the text-based user interface of the program. It is delimited by the `![textui]`! header in the configuration file. Available directives, along with their default values, are as follows:


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────